iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
Modern Web

別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站系列 第 8

Day 08|WebMCP Tool 該怎麼設計?把「點按鈕」改成「搜尋活動」

  • 分享至 

  • xImage
  •  

Day 08|WebMCP Tool 該怎麼設計?把「點按鈕」改成「搜尋活動」

安安~我是ChiYu~

昨天才把二十道測試題出好,今天輪到我寫第一份答案。我從最單純的搜尋活動開始,打開
頁面,看著那顆藍色的「搜尋活動」按鈕,很順手地寫下:

click_blue_search_button
description: 點擊右側藍色按鈕開始搜尋

有一說一,這個名字很好懂,懂到連自動化測試都知道下一步要按哪裡。問題是,我現在設計
的是給 Agent 使用的 Tool,不是替滑鼠找一份新工作。

這份說明把顏色、位置與點擊方式交代得很完整,卻沒回答更重要的事:它能搜尋什麼、接受
哪些條件、回傳哪些資料,又會不會改變使用者看得見的畫面。按鈕只要換顏色、移到左邊,
或被 Enter 鍵取代,這支 Tool 的名字就開始說謊。

使用者真正想做的是「依條件找活動」。所以我沒有繼續替
click_blue_search_button 補 schema,而是把它刪掉,從任務本身重新寫一次。

從按鈕說明改寫成 search_events Tool contract

圖 1:左邊的寫法綁住按鈕外觀;右邊的 search_events 固定任務、輸入、結果與副作用邊界。

這張圖比較的是 Tool 描述的層次:左邊記錄「目前這版 UI 剛好怎麼操作」,右邊說明
「使用者要完成的事」。後者能撐過改版,前者會跟著按鈕一起搬家。

search_events 描述搜尋任務,不綁按鈕顏色與位置

重新整理後,我先替 search_events 寫下完整的能力邊界:

欄位 決定
name search_events
何時使用 使用者要依關鍵字、地點、費用或程度尋找公開活動
input querylocationpricelevel,全部可省略
result count 與活動摘要陣列
UI 更新使用者可見的活動列表
副作用 唯讀;不收藏、不報名
找不到 成功回傳 count: 0, events: []
服務失敗 回傳可分類、可重試的錯誤,不假裝成空結果

這時我還沒有決定要用 Declarative 或 Imperative API,因為那是實作方式。現在更急的是把
任務說清楚:搜尋只負責找公開活動,結果要同步回畫面,而且絕對不會順手替使用者收藏或
報名。

正式 description 也不再教 Agent 怎麼點畫面:

依關鍵字、地點、費用與程度搜尋目前公開活動,
並更新使用者可見的活動列表。

未來按鈕就算改名成「探索場次」,這段 description 仍然成立,Tool 名稱也不需要跟著改。

input schema 只接受四種搜尋條件

任務名稱穩定下來後,下一個麻煩是參數。假如 schema 只放一個沒有規則的 filters,Agent
當然很自由;server 收到 cityCodewhereautoRegister 時也會自由到不知道該怎麼辦。

這個專案把搜尋輸入固定成四個欄位:

{
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "query": {
      "type": "string",
      "maxLength": 100,
      "description": "公開活動標題或摘要中的關鍵字。"
    },
    "location": {
      "type": "string",
      "enum": ["taipei", "kaohsiung", "online"]
    },
    "price": {
      "type": "string",
      "enum": ["free", "paid"]
    },
    "level": {
      "type": "string",
      "enum": ["beginner", "intermediate", "advanced"]
    }
  }
}

我沒有使用 cityCode=1feeType=0 這種還得另外翻譯的值。taipeifree
beginner 可以直接從 Prompt 對應,人類看 trace 時也不必先找代碼表。

additionalProperties: false 則是這次很明確的取捨:搜尋只接受這四個欄位,Agent 不能
自己補上 limitsortByautoRegister。少一點「你猜我收不收」,後面的驗證會輕鬆
很多。

不過,schema 只是契約,不是安全邊界。
Chrome WebMCP 最佳實務
也建議程式端繼續驗證輸入,因為模型不保證每次都乖乖照著 schema 送資料。

AgentReady Events 的 server 因此會再檢查一次:

  • query 最多 100 字。
  • locationpricelevel 必須在允許的 enum 內。
  • 無效條件回傳 400 VALIDATION_ERROR

Tool schema 幫 Agent 組出合理 input;真正決定資料能不能執行的,仍然是 server validation。

result 回傳活動摘要,下一支 Tool 才接得下去

input 整理好後,我原本可以只回一句:

{ "message": "搜尋完成" }

這句話沒有錯,只是幾乎沒用。Agent 不知道找到幾場,也拿不到下一步需要的活動 ID。使用者
若接著問「第一場幾點開始」,整段流程只好重新猜一次。

所以 search_events 會回傳 count 與公開活動摘要:

{
  "count": 1,
  "events": [
    {
      "id": "evt-webmcp-intro",
      "url": "/events/evt-webmcp-intro",
      "title": "WebMCP 入門工作坊",
      "summary": "從語意 HTML 到第一個網站 Tool。",
      "startsAt": "2027-01-23T10:00:00+08:00",
      "location": "taipei",
      "price": "free",
      "level": "beginner"
    }
  ]
}

id 可以交給下一支 Tool 取得詳情,url 則讓 Agent 使用網站提供的 route,不必自己拼
網址。結果只放公開搜尋需要的欄位,Email、內部資料與完整報名物件都不會跟著出門。

這樣一來,昨天題庫中的 ORD-01 才有機會照順序完成:先用 search_events 找到活動,再把
同一個 opaque ID 交給 get_event_details。如果 result 沒有 ID,多步驟任務從第一步就已經
斷線。

查無資料、服務失敗與輸入錯誤不能都回空陣列

搜尋結果是空的,不一定代表真的沒有活動。也可能是上游服務暫時失敗,或 Agent 傳進來的
enum 根本不合法。三種狀態如果都回 events: [],Agent 只會很有禮貌地把系統故障介紹成
「目前沒有符合的活動」。

因此 result contract 把它們分開:

沒有符合活動 → count 0,成功
上游暫時失敗 → TEMPORARY_FAILURE,retryable true
輸入 enum 無效 → INVALID_INPUT,retryable false

第一種可以請使用者調整搜尋條件;第二種可以稍後重試;第三種則應修正 input。錯誤名稱不是
為了讓 JSON 看起來正式,而是要讓 Agent 知道下一步能做什麼。

用三個問題與 11 項測試檢查 search_events 契約

規格寫完後,我用三個問題回頭檢查:

  1. 把 name 遮住,只讀 description,仍能判斷什麼時候該用嗎?
  2. 每個 input 都能從使用者語句取得,或在沒有條件時合理省略嗎?
  3. result 能回答使用者,也能安全交給下一支 Tool 嗎?

接著我跑了一輪聚焦測試,鎖定正式名稱、四個搜尋欄位、五 Tool catalog 與 evidence 規則,
共 11 項通過。這份結果只證明 contract 與目前程式一致,證據仍停在 E2 harness;Chrome
是否看見 Tool、Agent 會不會從自然語言選中它,都還得在後面用真實環境回答。

今天,我把畫面上的藍色按鈕整理成一項不依賴外觀的搜尋任務。開頭那支
click_blue_search_button 也正式退場,算是替明天省下一點麻煩。

因為活動網站還有詳情、收藏、報名與取消。如果每顆按鈕都照同樣方式包成 Tool,catalog
很快就會變成 UI 元件戶口名簿。明天我會把所有候選能力攤開,判斷哪些值得保留、哪些只該
留在人類介面裡。


上一篇
Day 07|Agent 偶爾答對還不夠,我先出二十道測試題
系列文
別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站8
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言